package com.qd.panda.service.third.tencent;

import com.cdqidi.config.mybatis.handler.GeoPoint;
import com.cdqidi.exception.ApiException;
import com.cdqidi.util.WindowQpsControl;
import com.jfinal.kit.HashKit;
import com.jfinal.weixin.sdk.kit.PaymentKit;
import com.qd.panda.service.third.tencent.result.GeoCoderResult;
import com.qd.panda.service.third.tencent.result.LocationResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 腾讯位置服务
 *
 * @author sjk
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class TencentLocationService {
    private static final String ERR_MSG = "逆地址解析出错: ";
    private static final String TRANSLATE = "https://apis.map.qq.com/ws/coord/v1/translate";
    private static final String GEOCODER = "https://apis.map.qq.com/ws/geocoder/v1/";
    private final ThirdTencentLocationConfig config;
    private final RestTemplate restTemplate;

    private static final WindowQpsControl QPS = new WindowQpsControl(5, 1, TimeUnit.SECONDS);

    /**
     * 逆地址解析
     *
     * @param geoPoint 经纬度
     * @return 行政区划码
     */
    public String getAdCode(GeoPoint geoPoint) {
        Map<String, String> map = new HashMap<>(8);
        map.put("location", geoPoint.build());
        map.put("get_poi", 0 + "");
        map.put("poi_options", "address_format=short");
        map.put("key", config.getKey());
        map.put("path", "/ws/geocoder/v1/");
        String sign = sign(map);
        map.put("sig", sign);
        String url = GEOCODER + "?" + PaymentKit.packageSign(map, false) + "&sig=" + URLEncoder.encode(sign, StandardCharsets.UTF_8);
        log.info("url: " + url);

        final GeoCoderResult geoCoderResult = Optional.ofNullable(restTemplate.getForObject(url, GeoCoderResult.class)).orElseThrow(() -> new ApiException(ERR_MSG + "lng:" + geoPoint.getLng() + ",lat: " + geoPoint.getLat()));
        map.clear();
        if (!geoCoderResult.isSuccess()) {
            throw new ApiException(ERR_MSG + geoCoderResult.getMessage());
        }
        GeoCoderResult.Result result = Optional.ofNullable(geoCoderResult.getResult()).orElseThrow(() -> new ApiException(ERR_MSG + geoCoderResult.getMessage()));
        return Optional.ofNullable(result.getAdInfo()).orElseThrow(() -> new ApiException(ERR_MSG + geoCoderResult.getMessage())).getAdCode();
    }


    /**
     * 单个坐标转换
     *
     * @param geoPoint 经纬度
     * @return 转换后的经纬度
     */
    public GeoPoint translate(GeoPoint geoPoint) {
        return translate(geoPoint, 3);
    }

    /**
     * @param geoPoint 经纬度
     * @param type        1 GPS坐标
     *                    2 sogou经纬度
     *                    3 baidu经纬度
     *                    4 mapbar经纬度
     *                    5 [默认]腾讯、google、高德坐标
     *                    6 sogou墨卡托
     * @return 转换后的经纬度
     */
    public GeoPoint translate(GeoPoint geoPoint, Integer type) {
        if (type < 1 || type > 6) {
            throw new ApiException("type类型错误");
        }
        List<GeoPoint> translates = translates(Collections.singletonList(geoPoint), type);
        return null == translates || translates.isEmpty() ? null : translates.get(0);
    }

    public List<GeoPoint> translates(List<GeoPoint> list) {
        return translates(list, 3);
    }

    /**
     * 批量坐标转换
     *
     * @param list 经纬度
     * @param type 1 GPS坐标
     *             2 sogou经纬度
     *             3 baidu经纬度
     *             4 mapbar经纬度
     *             5 [默认]腾讯、google、高德坐标
     *             6 sogou墨卡托
     * @return 转换后的经纬度
     */
    public List<GeoPoint> translates(List<GeoPoint> list, Integer type) {
        if (type < 1 || type > 6) {
            throw new ApiException("type类型错误");
        }
        String locations = GeoPoint.build(list);
        Map<String, String> map = new HashMap<>(8);
        map.put("locations", locations);
        map.put("type", String.valueOf(type));
        map.put("key", config.getKey());
        map.put("path", "/ws/coord/v1/translate");
        String sign = sign(map);
        map.put("sig", sign);
        String url = TRANSLATE + "?" + PaymentKit.packageSign(map, false) + "&sig=" + URLEncoder.encode(sign, StandardCharsets.UTF_8);
        waited();
        final LocationResult locationResult = Optional.ofNullable(restTemplate.getForObject(url, LocationResult.class)).orElseThrow(() -> new ApiException("坐标转换异常:[" + locations + "]"));
        map.clear();
        if (!locationResult.isSuccess()) {
            throw new ApiException("请求坐标转换接口出错: " + locationResult.getMessage());
        }
        List<GeoPoint> resultLocations = locationResult.getLocations();
        if (null == resultLocations || resultLocations.isEmpty()) {
            throw new ApiException("坐标转换异常: " + locationResult.getMessage());
        }
        return resultLocations;
    }


    private String sign(Map<String, String> map) {
        String path = map.get("path");
        if (!StringUtils.hasLength(path)) {
            throw new ApiException("path不能为空");
        }
        map.remove("path");
        String s = PaymentKit.packageSign(map, false);
        String stringSignTemp = path + "?" + s + config.getSecretKey();
        return HashKit.md5(stringSignTemp).toLowerCase();
    }

    private void waited() {
        try {
            if (!QPS.isPass()) {
                synchronized (this) {
                    this.wait(5);
                    waited();
                }
            }
        } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
        }
    }
}
